package com.openwater.tools;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import android.content.res.Resources;

import com.openwater.core.ModelObject;

/**
 * 
 * @author fuxp
 * @date 2012-6-8
 * @version 1.0 Copyright Corporation 2013,fuxp, All Rights Reserved
 * @since
 */
public class ModelLoader {

    private ModelLoader() {
    }

    /**
     * load model object's vertex from obj file ,and auto calculate the vertex's
     * Average normal
     * 
     * @param r
     *            resource reference
     * @param fname
     *            obj's file name
     * @param programid
     *            shader's id
     * 
     * @return
     */
    public static ModelObject loadFromFile(Resources r, String fname, int programid) {
        ModelObject lo = null;
        // original vertex form obj file
        ArrayList<Float> alv = new ArrayList<Float>();
        // original texture form obj file
        ArrayList<Float> alt = new ArrayList<Float>();

        // result vertex list according to face
        ArrayList<Float> alvResult = new ArrayList<Float>();
        // result texture list according to face
        ArrayList<Float> altResult = new ArrayList<Float>();

        // face index list form obj file
        ArrayList<Integer> alFaceIndex = new ArrayList<Integer>();
        //
        HashMap<Integer, HashSet<Normal>> hmn = new HashMap<Integer, HashSet<Normal>>();

        try {
            InputStream in = r.getAssets().open(fname);
            InputStreamReader isr = new InputStreamReader(in);
            BufferedReader br = new BufferedReader(isr);
            String temps = null;
            // scan the file ,read it line by line ,according to different type
            // difference thing
            while ((temps = br.readLine()) != null) {
                // split string line by space
                String[] tempsa = temps.split("[ ]+");
                // this line is vertex
                if (tempsa[0].trim().equals("v")) {
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                } else // this line is texture
                if (tempsa[0].trim().equals("vt")) {
                    // alt.add(Float.parseFloat(tempsa[1])/2.0f);
                    // alt.add(Float.parseFloat(tempsa[2])/2.0f);
                    alt.add(Float.parseFloat(tempsa[1]));
                    alt.add(1 - Float.parseFloat(tempsa[2]));
                } else // this is triangle's face index
                if (tempsa[0].trim().equals("f")) {

                    int[] index = new int[3];// x,y,z coordinate index
                    index[0] = Integer.parseInt(tempsa[1].split("/")[0]) - 1;
                    float x0 = alv.get(3 * index[0]);
                    float y0 = alv.get(3 * index[0] + 1);
                    float z0 = alv.get(3 * index[0] + 2);
                    alvResult.add(x0);
                    alvResult.add(y0);
                    alvResult.add(z0);
                    index[1] = Integer.parseInt(tempsa[2].split("/")[0]) - 1;
                    float x1 = alv.get(3 * index[1]);
                    float y1 = alv.get(3 * index[1] + 1);
                    float z1 = alv.get(3 * index[1] + 2);
                    alvResult.add(x1);
                    alvResult.add(y1);
                    alvResult.add(z1);
                    index[2] = Integer.parseInt(tempsa[3].split("/")[0]) - 1;
                    float x2 = alv.get(3 * index[2]);
                    float y2 = alv.get(3 * index[2] + 1);
                    float z2 = alv.get(3 * index[2] + 2);
                    alvResult.add(x2);
                    alvResult.add(y2);
                    alvResult.add(z2);
                    alFaceIndex.add(index[0]);
                    alFaceIndex.add(index[1]);
                    alFaceIndex.add(index[2]);
                    float vxa = x1 - x0;
                    float vya = y1 - y0;
                    float vza = z1 - z0;
                    float vxb = x2 - x0;
                    float vyb = y2 - y0;
                    float vzb = z2 - z0;
                    float[] vNormal = vectorNormal(getCrossProduct(vxa, vya, vza, vxb, vyb, vzb));
                    for (int tempInxex : index) {
                        HashSet<Normal> hsn = hmn.get(tempInxex);
                        if (hsn == null) {
                            hsn = new HashSet<Normal>();
                        }
                        hsn.add(new Normal(vNormal[0], vNormal[1], vNormal[2]));
                        hmn.put(tempInxex, hsn);
                    }
                    int indexTex = Integer.parseInt(tempsa[1].split("/")[1]) - 1;
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));
                    indexTex = Integer.parseInt(tempsa[2].split("/")[1]) - 1;
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));
                    indexTex = Integer.parseInt(tempsa[3].split("/")[1]) - 1;
                    altResult.add(alt.get(indexTex * 2));
                    altResult.add(alt.get(indexTex * 2 + 1));
                }
            }
            int size = alvResult.size();
            float[] vXYZ = new float[size];
            for (int i = 0; i < size; i++) {
                vXYZ[i] = alvResult.get(i);
            }
            float[] nXYZ = new float[alFaceIndex.size() * 3];
            int c = 0;
            for (Integer i : alFaceIndex) {
                HashSet<Normal> hsn = hmn.get(i);
                float[] tn = Normal.getAverage(hsn);
                nXYZ[c++] = tn[0];
                nXYZ[c++] = tn[1];
                nXYZ[c++] = tn[2];
            }
            size = altResult.size();
            float[] tST = new float[size];
            for (int i = 0; i < size; i++) {
                tST[i] = altResult.get(i);
            }
            lo = new ModelObject(programid, vXYZ, nXYZ, tST);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return lo;
    }

    /** calculate the Cross Product of vector */
    public static float[] getCrossProduct(float x1, float y1, float z1, float x2, float y2, float z2) {
        float A = y1 * z2 - y2 * z1;
        float B = z1 * x2 - z2 * x1;
        float C = x1 * y2 - x2 * y1;
        return new float[] { A, B, C };
    }

    /** normalized the vector */
    public static float[] vectorNormal(float[] vector) {
        float module = (float) Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2]
                * vector[2]);
        return new float[] { vector[0] / module, vector[1] / module, vector[2] / module };
    }

    /**
     * Normal class
     * 
     * @author fuxp
     */
    private static class Normal {
        private static final float DIFF = 0.0000001f;

        private float nx;
        private float ny;
        private float nz;

        public Normal(float nx, float ny, float nz) {
            this.nx = nx;
            this.ny = ny;
            this.nz = nz;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Normal) {
                Normal tn = (Normal) o;
                if (Math.abs(nx - tn.nx) < DIFF && Math.abs(ny - tn.ny) < DIFF
                        && Math.abs(ny - tn.ny) < DIFF) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        /**
         * override hash code
         */
        @Override
        public int hashCode() {
            return 1;
        }

        /**
         * Average normal vector
         * 
         * @param sn
         * @return
         */
        public static float[] getAverage(Set<Normal> sn) {
            float[] result = new float[3];
            for (Normal n : sn) {
                result[0] += n.nx;
                result[1] += n.ny;
                result[2] += n.nz;
            }
            return vectorNormal(result);
        }
    }
}
